#define _GNU_SOURCE
#include "ax_timer.h"
#include "ax_epoll.h"
#include "ax_fd.h"
#include "ax_hashtable.h"
#include "ax_list.h"
#include "ax_printf.h"
#include "ax_thread.h"
#include "com.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#define MAX_TIMER_IDS 200


typedef struct
{
    ax_fd*        tfd;
    ax_timer_cb_t cb;
    long          duration_ms;
    int           param1;
    int           param2;
    AX_TIMER_MODE mode;
    bool          running;
} ax_timer;

typedef struct
{
    ax_epoll* epoll;
    ax_thread_t   loop_thread;
    ax_hashtable* timers;
    ax_mutex_t*   mutex;
    bool          inited;
} ax_timer_mgr;

static ax_timer_mgr g_tmgr = {NULL, 0, NULL, NULL, false };

static void*     loop_thread_fun(void* data);
static int       fd_cb(ax_fd* fd, unsigned int what);
static ax_timer* get_timer(int tid);
static void      add_timer(ax_timer* t);
static void      del_timer(int tid);

/**
 * @brief timer timer system init
 * @param loop  true, will run timer loop in internal thread
 * @return
 */
bool ax_timer_mgr_init(bool loop)
{
    ax_check_and_return_false(false == g_tmgr.inited, NULL);

    g_tmgr.timers = ax_hashtable_create(HT_KEY_CONST | HT_VALUE_CONST);
    ax_check_and_return_false(g_tmgr.timers, NULL);
    g_tmgr.epoll = ax_epoll_create();
    g_tmgr.mutex = ax_mutex_create();
    if(NULL == g_tmgr.mutex)
    {
        ax_hashtable_destory(g_tmgr.timers);
        return false;
    }

    if(loop)
    {
        g_tmgr.loop_thread = ax_thread_start(loop_thread_fun, &g_tmgr);
    }
    else
    {
        g_tmgr.loop_thread = -1;
    }
    g_tmgr.inited = true;
    return true;
}

/**
 * @brief free resource
 */
void ax_timer_mgr_free()
{
    ax_check_and_return(g_tmgr.inited, NULL);

    if(g_tmgr.loop_thread != -1)
    {
        ax_thread_cancel(&g_tmgr.loop_thread);
        ax_thread_wait(&g_tmgr.loop_thread);
    }

    int*      tid;
    ax_timer* timer;
    ax_hashtable_foreach(tid, timer, g_tmgr.timers)
    {
        ax_debug("free tid:%d\n", *tid);
        FREE(timer);
    }
    ax_hashtable_destory(g_tmgr.timers);
    ax_mutex_destroy(g_tmgr.mutex);
    ax_epool_destory(g_tmgr.epoll);
}

/**
 * @brief check timer list, and trigger callbak if timer expire
 * @return
 *
 * can not call if other plase use ax_epool_main
 */
bool ax_timer_main()
{
    ax_epoll_main(g_tmgr.epoll, 1000);
    return true;
}

int ax_timer_create(ax_timer_cb_t cb)
{
    ax_timer* t = ALLOC(ax_timer);
    if(NULL == t)
        return -1;

    t->tfd     = ax_fd_create(-1, AX_FD_READ, fd_cb, t);
    t->tfd->fd = timerfd_create(CLOCK_MONOTONIC, 0);  //创建定时器
    if(-1 == t->tfd->fd)
    {
        FREE(t);
        ax_error("create timer fd fail \r\n");
        return -1;
    }
    t->cb          = cb;
    t->duration_ms = 1000;
    t->param1      = 0;
    t->param2      = 0;
    t->running     = false;
    add_timer(t);
    return t->tfd->fd;
}

bool ax_timer_destory(int tid)
{
    if(-1 == tid)
        return false;

    ax_timer_stop(tid);

    ax_timer* t = get_timer(tid);
    if(NULL == t)
    {
        ax_error("timer estory error!\n");
        return false;
    }
    ax_epoll_unregister(g_tmgr.epoll, t->tfd);
    ax_fd_destroy(t->tfd);
    del_timer(tid);
    FREE(t);
    return true;
}

bool ax_timer_start(int tid, long duradion_ms, AX_TIMER_MODE mode, int param1, int param2)
{
    ax_timer* t = get_timer(tid);
    ax_check_and_return_false(t, NULL);

    t->duration_ms = duradion_ms;
    t->param1      = param1;
    t->param2      = param2;
    t->mode        = mode;
    t->running     = true;

    struct itimerspec time_intv;
    time_intv.it_value.tv_sec  = duradion_ms / 1000;
    time_intv.it_value.tv_nsec = duradion_ms % 1000 * 1000;  // first timeout

    if(en_TIMER_LOOP == mode)
    {
        time_intv.it_interval.tv_sec  = time_intv.it_value.tv_sec;
        time_intv.it_interval.tv_nsec = time_intv.it_value.tv_nsec;  // other timeout
    }
    else
    {
        time_intv.it_interval.tv_sec  = 0;
        time_intv.it_interval.tv_nsec = 0;  // no loop
    }

    ax_epoll_register(g_tmgr.epoll, t->tfd);
    timerfd_settime(t->tfd->fd, 0, &time_intv, NULL);  //启动定时器

    return true;
}

bool ax_timer_stop(int tid)
{
    ax_timer* t = get_timer(tid);
    ax_check_and_return_false(t, NULL);
    timerfd_settime(t->tfd->fd, 0, NULL, NULL);  //启动定时器
    t->running = false;
    ax_epoll_unregister(g_tmgr.epoll, t->tfd);
    return true;
}

bool ax_timer_reset(int tid)
{
    ax_timer* t = get_timer(tid);
    ax_check_and_return_false(t, NULL);
    ax_timer_start(tid, t->duration_ms, t->mode, t->param1, t->param2);
    return true;
}

bool ax_timer_is_run(int tid)
{
    ax_timer* t = get_timer(tid);
    if(NULL == t)
    {
        return false;
    }
    return t->running;
}

static ax_timer* get_timer(int tid)
{
    ax_mutex_lock(g_tmgr.mutex);
    ax_timer* t = ax_hashtable_get(g_tmgr.timers, ( const char* )&tid, sizeof(tid), NULL);
    ax_mutex_unlock(g_tmgr.mutex);
    return t;
}

static void add_timer(ax_timer* t)
{
    ax_mutex_lock(g_tmgr.mutex);
    ax_hashtable_insert(g_tmgr.timers, ( const char* )&t->tfd->fd, sizeof(t->tfd->fd), t, sizeof(t));
    ax_mutex_unlock(g_tmgr.mutex);
}

static void del_timer(int tid)
{
    ax_mutex_lock(g_tmgr.mutex);
    ax_hashtable_remove(g_tmgr.timers, ( const char* )&tid, sizeof(tid));
    ax_mutex_unlock(g_tmgr.mutex);
}

static int fd_cb(ax_fd* fd, unsigned int what)
{
    int      ret;
    uint64_t value;
    if(NULL == fd)
        return -1;
    ax_timer* t = get_timer(fd->fd);
    if(NULL == t)
        return -1;
    if(t->cb)
    {
        t->cb(fd->fd, t->param1, t->param2);
    }

    if(en_TIMER_NOLOOP == t->mode)
    {
        t->running = false;
        ax_epoll_unregister(g_tmgr.epoll, fd);
    }
    read(fd->fd, &value, sizeof(value));
    return 0;
}

static void* loop_thread_fun(void* data)
{
    ax_timer_mgr* mgr = ( ax_timer_mgr* )data;
    ax_info("timer thread run!\n");
    while(true)
    {
        if(ax_thread_is_cancel(&mgr->loop_thread))
            break;
        ax_timer_main();
    }
    return NULL;
}
